@file:Suppress("unused", "SpellCheckingInspection")

package com.example.base.ext

import android.app.Activity
import android.content.*
import android.graphics.*
import android.net.Uri
import android.os.*
import android.provider.MediaStore
import android.provider.Settings
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.webkit.MimeTypeMap
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.annotation.*
import androidx.annotation.IntRange
import androidx.collection.ArrayMap
import androidx.core.content.ContextCompat
import androidx.core.view.MarginLayoutParamsCompat
import androidx.core.view.ViewCompat
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.bumptech.glide.load.resource.bitmap.Rotate
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.coorchice.library.SuperTextView
import com.coorchice.library.utils.LogUtils
import com.example.base.BaseApp
import com.example.base.R
import com.example.base.constant.RequestCode
import com.example.base.databinding.LayoutAlertDialogBinding
import com.example.base.tools.BuildProperties
import com.example.base.tools.JsonTool
import com.example.base.tools.MsgImageTarget
import com.example.base.ui.dialog.ConfirmDialog
import com.google.android.material.bottomnavigation.BottomNavigationItemView
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.therouter.TheRouter
import com.therouter.router.Navigator
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.*
import java.net.*
import java.security.MessageDigest
import java.util.*
import java.util.concurrent.CountDownLatch
import kotlin.random.Random.Default.nextInt


/**
 * 加载时显示进度 View 的抽象接口
 */
interface LoadingView {

    /**
     * 显示进度条
     */
    fun showProgress()

    /**
     * 移除进度条
     */
    fun hideProgress()

}

/**
 * 获取颜色资源
 * @param redId 资源 id
 */
fun Context.getColorRes(@ColorRes redId: Int) = ContextCompat.getColor(this, redId)

/**
 * 关闭当前 Activity 并传递值到上一个页面
 * @param results 需传递的值, 支持 Intent 支持的类型, 使用不支持类型时会抛出异常
 */
fun Activity.finishWithResult(results: MutableMap<String, out Any> = mutableMapOf()) {
    val intent = Intent()
    results.forEach { result ->
        when (result.value) {
            is Boolean -> intent.putExtra(result.key, result.value as Boolean)
            is Byte -> intent.putExtra(result.key, result.value as Byte)
            is Char -> intent.putExtra(result.key, result.value as Char)
            is Short -> intent.putExtra(result.key, result.value as Short)
            is Int -> intent.putExtra(result.key, result.value as Int)
            is Long -> intent.putExtra(result.key, result.value as Long)
            is Float -> intent.putExtra(result.key, result.value as Float)
            is Double -> intent.putExtra(result.key, result.value as Double)
            is String -> intent.putExtra(result.key, result.value as String)
            is CharSequence -> intent.putExtra(result.key, result.value as CharSequence)
            is Parcelable -> intent.putExtra(result.key, result.value as Parcelable)
            is Bundle -> intent.putExtra(result.key, result.value as Bundle)
            is Serializable -> intent.putExtra(result.key, result.value as Serializable)
            is BooleanArray -> intent.putExtra(result.key, result.value as BooleanArray)
            is ByteArray -> intent.putExtra(result.key, result.value as ByteArray)
            is ShortArray -> intent.putExtra(result.key, result.value as ShortArray)
            is CharArray -> intent.putExtra(result.key, result.value as CharArray)
            is IntArray -> intent.putExtra(result.key, result.value as IntArray)
            is LongArray -> intent.putExtra(result.key, result.value as LongArray)
            is FloatArray -> intent.putExtra(result.key, result.value as FloatArray)
            is DoubleArray -> intent.putExtra(result.key, result.value as DoubleArray)
            else -> throw IllegalArgumentException("Result type ${result.value::class.java.name} not be supported!")
        }
    }
    setResult(Activity.RESULT_OK, intent)
    finish()
}

/**
 * 显示吐司
 * @param content 吐司的内容
 * @param duration 吐司显示的时长, 默认是 [Toast.LENGTH_LONG]
 */
fun Activity.toast(
    content: String,
    @IntRange(
        from = Toast.LENGTH_SHORT.toLong(),
        to = Toast.LENGTH_LONG.toLong()
    ) duration: Int = Toast.LENGTH_LONG,
) {
    runOnUiThread {
        toast(this, content, duration)
    }
}

/**
 * 显示吐司
 * @param content 吐司的内容
 * @param duration 吐司显示的时长, 默认是 [Toast.LENGTH_LONG]
 */
fun Fragment.toast(
    content: String,
    @IntRange(
        from = Toast.LENGTH_SHORT.toLong(),
        to = Toast.LENGTH_LONG.toLong()
    ) duration: Int = Toast.LENGTH_LONG,
) {
    requireActivity().runOnUiThread {
        toast(requireActivity(), content, duration)
    }
}

/**
 * 统一吐司风格的方法
 * @param context 上下文
 * @param content 吐司的内容
 * @param duration 吐司显示的时长, 默认是 [Toast.LENGTH_LONG]
 */
private fun toast(
    context: Context,
    content: String,
    @IntRange(
        from = Toast.LENGTH_SHORT.toLong(),
        to = Toast.LENGTH_LONG.toLong()
    ) duration: Int = Toast.LENGTH_LONG,
) {
    Toast.makeText(context, content, duration).show()
}


/*********************************************************************************/
/**                                                                             **/
/**                                 View Extensions                             **/
/**                                                                             **/
/*********************************************************************************/

/**
 * 给 View 设置左边距
 * @param resId 资源 id
 */
fun View.setMarginStart(@DimenRes resId: Int) = MarginLayoutParamsCompat.setMarginStart(
    layoutParams as ViewGroup.MarginLayoutParams,
    context.resources.getDimensionPixelSize(resId)
)

/**
 * 给 View 设置右边距
 * @param resId 资源 id
 */
fun View.setMarginEnd(@DimenRes resId: Int) = MarginLayoutParamsCompat.setMarginEnd(
    layoutParams as ViewGroup.MarginLayoutParams,
    context.resources.getDimensionPixelSize(resId)
)

/**
 * 给 View 设置上边距
 * @param resId 资源 id
 */
fun View.setMarginTop(@DimenRes resId: Int) {
    val params = layoutParams as ViewGroup.MarginLayoutParams
    params.topMargin = context.resources.getDimensionPixelSize(resId)
    layoutParams = params
}

/**
 * 给 View 设置下边距
 * @param resId 资源 id
 */
fun View.setMarginBottom(@DimenRes resId: Int) {
    val params = layoutParams as ViewGroup.MarginLayoutParams
    params.bottomMargin = context.resources.getDimensionPixelSize(resId)
    layoutParams = params
}

/**
 * 给 View 设置左边距
 * @param pixelSize 像素值
 */
fun View.setMarginStartPixel(pixelSize: Int) = MarginLayoutParamsCompat.setMarginStart(
    layoutParams as ViewGroup.MarginLayoutParams,
    pixelSize
)

/**
 * 给 View 设置右边距
 * @param pixelSize 像素值
 */
fun View.setMarginEndPixel(pixelSize: Int) = MarginLayoutParamsCompat.setMarginEnd(
    layoutParams as ViewGroup.MarginLayoutParams,
    pixelSize
)

/**
 * 给 View 设置上边距
 * @param pixelSize 像素值
 */
fun View.setMarginTopPixel(pixelSize: Int) {
    val params = layoutParams as ViewGroup.MarginLayoutParams
    params.topMargin = pixelSize
    layoutParams = params
}

/**
 * 给 View 设置下边距
 * @param pixelSize 像素值
 */
fun View.setMarginBottomPixel(pixelSize: Int) {
    val params = layoutParams as ViewGroup.MarginLayoutParams
    params.bottomMargin = pixelSize
    layoutParams = params
}

/**  给 View 设置高度  **/
fun View.setLayoutHeight(height: Int) {
    val layoutParams = this.layoutParams
    layoutParams.height = height
    this.layoutParams = layoutParams
}

/**  获取当前 View 的高度  **/
fun View.getLayoutHeight() = this.layoutParams.height

/**  给 View 设置宽度  **/
fun View.setLayoutWidth(width: Int) {
    val layoutParams = this.layoutParams
    layoutParams.width = width
    this.layoutParams = layoutParams
}

/**  获取当前 View 的宽度  **/
fun View.getLayoutWidth() = this.layoutParams.width

/**
 * 给 TextView 的上方图标并设置颜色
 * @param iconId 图标资源 id
 * @param redId 图标颜色资源 id, 默认使用图标自己的颜色
 */
fun TextView.setTopIcon(@DrawableRes iconId: Int, @ColorRes redId: Int = 0) {
    val icon = VectorDrawableCompat.create(context.resources, iconId, context.theme)
    if (redId != 0) icon?.setTint(ContextCompat.getColor(context, redId))
    this.setCompoundDrawablesRelativeWithIntrinsicBounds(null, icon, null, null)
}

/**
 * 给 TextView 的右侧图标并设置颜色
 * @param iconId 图标资源 id
 * @param redId 图标颜色资源 id, 默认使用图标自己的颜色
 */
fun TextView.setRightIcon(@DrawableRes iconId: Int, @ColorRes redId: Int = 0) {
    val icon = VectorDrawableCompat.create(context.resources, iconId, context.theme)
    if (redId != 0) icon?.setTint(ContextCompat.getColor(context, redId))
    this.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, icon, null)
}

/**
 * 设置一个 Vector 资源到 ImageView 中
 * @param resId Vector 的资源 id
 */
fun ImageView.setVectorDrawable(@DrawableRes resId: Int) {
    val vectorDrawableCompat = VectorDrawableCompat.create(this.resources, resId, null)
    this.setImageDrawable(vectorDrawableCompat)
}

/**
 * 给 ImageView 设置圆角图片
 * @param imgUrl 图片的 URL 地址
 * @param corners 图片的圆角尺寸
 */
fun ImageView.setCornersImageUrl(imgUrl: String?, corners: Int) {
    Glide.with(context)
        .load(imgUrl)
        .placeholder(R.drawable.bg_empty)
        .error(R.drawable.bg_empty)
        .disallowHardwareConfig()
        .apply(RequestOptions.bitmapTransform(RoundedCorners(corners)))
        .dontAnimate()
        .into(this)
}

/**
 * 给 ImageView 设置圆图片
 * @param imgUrl 图片的 URL 地址
 * @param corners 图片的圆角尺寸
 */
fun ImageView.setCircleCropImageUrl(imgUrl: String?) {
    Glide.with(context)
        .load(imgUrl)
        .placeholder(R.drawable.header_img)
        .error(R.drawable.header_img)
        .disallowHardwareConfig()
        .apply(RequestOptions.bitmapTransform(CircleCrop()))
        .dontAnimate()
        .into(this)
}

/**
 * 给 ImageView 设置Gif图片
 * @param imgUrl 图片的 URL 地址
 */
fun ImageView.setGifImageUrl(imgUrlGif: String?, corners: Int) {
    Glide.with(context)
        .asGif()
        .load(imgUrlGif)
        .disallowHardwareConfig()
        .apply(RequestOptions.bitmapTransform(RoundedCorners(corners)))

        .into(this)
}

/**
 * 给 ImageView 设置Gif图片
 * @param imgUrl 图片的 URL 地址
 */
fun ImageView.setThumbnailImageUrl(imgUrlGif: String?, corners: Int) {
    Glide.with(context)
        .load(imgUrlGif)
        .thumbnail()
        .disallowHardwareConfig()
        .apply(RequestOptions.bitmapTransform(RoundedCorners(corners)))

        .into(this)
}

/**
 * 给 ImageView 设置Gif图片
 * @param imgUrl 图片的 URL 地址
 */
fun ImageView.setBigAndLongImageUrl(imgUrlGif: String?, corners: Int) {
    Glide.with(context)
        .asBitmap()
        .load(imgUrlGif)
        .override(500, 500)
        .disallowHardwareConfig()
        .apply(RequestOptions.bitmapTransform(RoundedCorners(corners)))

        .into(MsgImageTarget(this))
}

/**
 * 给 ImageView 设置圆角图片
 * @param imgUrl 图片的 URL 地址
 * @param corners 图片的圆角尺寸
 */
fun ImageView.setCornersPlaceHolderImageUrl(imgUrl: String, corners: Int, placeHolder: Int) {
    Glide.with(context)
        .load(imgUrl)
        .placeholder(placeHolder)
        .error(placeHolder)

        .disallowHardwareConfig()
        .apply(RequestOptions.bitmapTransform(RoundedCorners(corners)))
        .dontAnimate()
        .into(this)
}

/**
 * 给 ImageView 设置图片并旋转图片角度
 * @param imgUrl 图片的 URL 地址
 * @param degreesToRotate 图片的旋转角度
 */
fun ImageView.setRotateImageUrl(imgUrl: String, degreesToRotate: Int) {
    Glide.with(context)
        .load(imgUrl)
        .disallowHardwareConfig()
        .apply(RequestOptions.bitmapTransform(Rotate(degreesToRotate)))
        .dontAnimate()
        .into(this)
}


/**
 * 显示底部导航某个索引位置的小圆点
 * @param index 目标索引位置
 * @param badgeViewId 小圆点 View 的 id
 */
fun BottomNavigationView.visibleBadgeViewAt(index: Int, @IdRes badgeViewId: Int, count: Int = 0) {
    val menuView = this.getChildAt(0) as BottomNavigationMenuView
    val itemView = menuView.getChildAt(index) as BottomNavigationItemView
    val badgeView = itemView.findViewById<SuperTextView>(badgeViewId) ?: return
    if (count > 0) badgeView.text = count.toString()
    badgeView.visibility = View.VISIBLE
}


/**
 * 隐藏底部导航某个索引位置的小圆点
 * @param index 目标索引位置
 * @param badgeViewId 小圆点 View 的 id
 */
fun BottomNavigationView.goneBadgeViewAt(index: Int, @IdRes badgeViewId: Int) {
    val menuView = this.getChildAt(0) as BottomNavigationMenuView
    val itemView = menuView.getChildAt(index) as BottomNavigationItemView
    val badgeView: View = itemView.findViewById(badgeViewId) ?: return
    badgeView.visibility = View.GONE
}


/*********************************************************************************/
/**                                                                             **/
/**                          Route Navigation Extensions                        **/
/**                                                                             **/
/*********************************************************************************/

/**
 * RecyclerView.Adapter 中的扩展函数, 用于在 Adapter 路由跳转 Activity 并携带参数
 * @param routePath 路由地址
 * @param params 传递到目标路由的参数, 默认为空参
 */
fun <VH : RecyclerView.ViewHolder> RecyclerView.Adapter<VH>.navigationTo(
    routePath: String,
    params: MutableMap<String, out Any> = mutableMapOf(),
) = navigationByRoute(routePath, params)


/**
 * 用于在 任何地方 进行路由跳转并携带参数
 * @param routePath 路由地址
 * @param params 传递到目标路由的参数, 默认为空参
 */
fun Any.navigationTo(
    routePath: String,
    params: MutableMap<String, out Any> = mutableMapOf(),
) = navigationByRoute(routePath, params)


/**
 * Activity 中的扩展函数, 用于在 Activity 路由跳转到目标 Activity 并携带参数
 * @param routePath 路由地址
 * @param params 传递到目标路由的参数, 默认为空参
 */
fun Activity.navigationTo(
    routePath: String,
    params: MutableMap<String, out Any> = mutableMapOf(),
) = navigationByRoute(routePath, params)

/**
 * Activity 中的扩展函数, 用于在 Activity 路由跳转到目标 Activity 并携带参数同时还可以获取目标页面的返回参数, 返回回调发生在 Activity 中
 * @param routePath 路由地址
 * @param params 传递到目标路由的参数, 默认为空参
 * @param requestCode 请求码
 */
fun Activity.navigationTo(
    routePath: String,
    requestCode: Int,
    params: MutableMap<String, out Any> = mutableMapOf(),
) {
    TheRouter.build(routePath).setParams(params).navigation(this, requestCode)
}

/**
 * Fragment 中的扩展函数, 用于在 Fragment 路由跳转到目标 Activity 并携带参数
 * @param routePath 路由地址
 * @param params 传递到目标路由的参数, 默认为空参
 */
fun Fragment.navigationTo(
    routePath: String,
    params: MutableMap<String, out Any> = mutableMapOf(),
) = navigationByRoute(routePath, params)

/**
 * Fragment 中的扩展函数, 用于在 Fragment 路由跳转到目标 Activity 并携带参数同时还可以获取目标页面的返回参数
 * @param routePath 路由地址
 * @param params 传递到目标路由的参数, 默认为空参
 * @param requestCode 请求码
 */
fun Fragment.navigationTo(
    routePath: String,
    requestCode: Int,
    params: MutableMap<String, out Any> = mutableMapOf(),
) = requireActivity().navigationTo(routePath, requestCode, params)

/**
 * 通用方法, 用于路由跳转
 * @param routePath 路由地址
 * @param params 传递到目标路由的参数, 默认为空参
 */
private fun navigationByRoute(
    routePath: String,
    params: MutableMap<String, out Any> = mutableMapOf(),
) {
    TheRouter.build(routePath).setParams(params).navigation()
}

/**  给 Aroute 的请求设置参数列表  **/
fun Navigator.setParams(params: MutableMap<String, out Any> = mutableMapOf()): Navigator {
    params.forEach {
        when (it.value) {
            is String -> this.withString(it.key, it.value as String)
            is Boolean -> this.withBoolean(it.key, it.value as Boolean)
            //  is Short -> this.withShort(it.key, it.value as Short)
            is Int -> this.withInt(it.key, it.value as Int)
            is Long -> this.withLong(it.key, it.value as Long)
            is Double -> this.withDouble(it.key, it.value as Double)
            is Byte -> this.withByte(it.key, it.value as Byte)
            is Char -> this.withChar(it.key, it.value as Char)
            is Float -> this.withFloat(it.key, it.value as Float)
            /* is CharSequence -> this.withCharSequence(
                 it.key,
                 it.value as CharSequence
             )*/
            is Parcelable -> this.withParcelable(it.key, it.value as Parcelable)
            else -> this.withObject(it.key, it.value)
        }
    }
    return this
}

/*********************************************************************************/
/**                                                                             **/
/**                            Screen Size Extensions                           **/
/**                                                                             **/
/*********************************************************************************/

/**  屏幕宽度值  **/
val Context.screenWidth: Int get() = applicationContext.resources.displayMetrics.widthPixels

/**  屏幕高度值  **/
val Context.screenHeight: Int get() = applicationContext.resources.displayMetrics.heightPixels

/**  dp 转 px  **/
fun Context.dip2px(dpValue: Float) = (dpValue * resources.displayMetrics.density + 0.5f)

/**  屏幕宽度值  **/
val Fragment.screenWidth: Int get() = requireActivity().screenWidth

/**  屏幕高度值  **/
val Fragment.screenHeight: Int get() = requireActivity().screenHeight


/*********************************************************************************/
/**                                                                             **/
/**                             ViewModel Extensions                            **/
/**                                                                             **/
/*********************************************************************************/

/**
 * 获取 ViewModel 对象
 * @param VM 指定 ViewModel 的类型
 * @param viewModelFactory 用于实例化 ViewModel 的 Factory
 *
 * 默认为 [ViewModelProvider.NewInstanceFactory()]
 *
 * 如若使用 AndroidViewModel 可使用 [ViewModelProvider.AndroidViewModelFactory.getInstance(this.application)]
 */
inline fun <reified VM : ViewModel> Fragment.getViewModel(
    viewModelFactory: ViewModelProvider.Factory
    = SavedStateViewModelFactory(requireActivity().application, this),
) = ViewModelProvider(
    this,
    viewModelFactory
)[VM::class.java]

/**
 * 获取 ViewModel 对象
 * @param VM 指定 ViewModel 的类型
 * @param viewModelFactory 用于实例化 ViewModel 的 Factory
 *
 * 默认为 [ViewModelProvider.NewInstanceFactory()]
 *
 * 如若使用 AndroidViewModel 可使用 [ViewModelProvider.AndroidViewModelFactory.getInstance(this.application)]
 */
@Suppress("SpellCheckingInspection")
inline fun <reified VM : ViewModel> ComponentActivity.getViewModel(
    viewModelFactory: ViewModelProvider.Factory
    = SavedStateViewModelFactory(this.application, this),
) = ViewModelProvider(
    this,
    viewModelFactory
)[VM::class.java]

/*inline fun <reified VM : ViewModel> ComponentActivity.lazyViewModel() = lazy {
    getViewModel<VM>()
}*/


/*********************************************************************************/
/**                                                                             **/
/**                               Dialog Extensions                             **/
/**                                                                             **/
/*********************************************************************************/

/**
 * 当 Dialog 进行初始化时的 dsl 封装
 * @param VDB 当前 Dialog 的 ViewDataBinding 对象
 */
class OnInitDialog<VDB : ViewDataBinding>(private val block: (binding: VDB, dialog: DialogFragment) -> Unit) {
    fun complete(binding: VDB, dialog: DialogFragment) {
        block(binding, dialog)
    }
}

/**
 * 弹出提示框
 * @param diaTitle 标题
 * @param content 内容
 * @param confirm 确认按钮的事件
 */
fun FragmentActivity.alertDialog(
    diaTitle: String,
    content: String,
    confirm: (dialog: DialogInterface) -> Unit = { it.dismiss() },
) {
    ConfirmDialog.Builder<LayoutAlertDialogBinding>().apply {
        onInitDialog = OnInitDialog { binding, dialog ->
            binding.builder = this
            binding.dialog = dialog

            binding.btnConfirm.setOnClickListener {
                onConfirm(dialog.requireDialog())
            }
        }
        title = diaTitle
        message = content
        onConfirm = confirm
        cancelText = ""
        build(supportFragmentManager)
    }
}


/**
 * 弹出确认框
 * @param diaTitle 标题
 * @param content 内容
 * @param confirmBtnText 确认按钮文字
 * @param cancelBtnText 取消按钮文字
 * @param confirm 确认按钮的事件, 当第二个参数为 true, 则用户点击的是确认按钮, 否则点击的为取消按钮
 */
fun FragmentActivity.confirmDialog(
    diaTitle: String,
    content: String,
    confirmBtnText: String = "确认",
    cancelBtnText: String = "取消",
    confirm: (dialog: DialogInterface, isConfirm: Boolean) -> Unit,
) {
    ConfirmDialog.Builder<LayoutAlertDialogBinding>().apply {
        onInitDialog = OnInitDialog { binding, dialog ->
            binding.builder = this
            binding.dialog = dialog
            binding.btnConfirm.setTextColor(resources.getColor(R.color.black))

            binding.btnConfirm.setOnClickListener {
                onConfirm(dialog.requireDialog())
            }
            binding.btnCancel.setOnClickListener {
                onCancel(dialog.requireDialog())
            }
        }
        title = diaTitle
        confirmText = confirmBtnText
        cancelText = cancelBtnText
        message = content
        onConfirm = { dialog -> confirm(dialog, true) }
        onCancel = { dialog -> confirm(dialog, false) }
        build(supportFragmentManager)
    }
}


/**  弹出引导用户打开 GPS 的弹窗  **/
fun FragmentActivity.openGPSDialog(cancalCallback: () -> Unit = {}) = confirmDialog(
    "系统提示",
    "您暂未开启位置服务, 将无法正常使用功能, 是否现在去开启 GPS 定位？"
) { dialog, isConfirm ->
    if (isConfirm) {
        val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
        startActivityForResult(intent, RequestCode.REQUEST_SETTINGS_LOCATION)
    } else cancalCallback()
    dialog.dismiss()
}

/**
 * 弹出底部 及联选择数据 Dialog
 * @param firstDataProvider Dialog 初次显示时的数据
 *//*
fun FragmentActivity.multistageSelectDialog(
    firstDataProvider: () -> List<ISelectAble>
): MultistageDataProvider {
    val dialog = BottomSelectDialog(this)
    dialog.init(this, false)
    dialog.setDataProvider { position, receiver ->
        if (position == 0) {
            receiver?.send(firstDataProvider())
        }
    }
    dialog.show()
    return MultistageDataProvider(dialog)
}

*/
/**
 * 及联选择数据 Dialog 的 及联数据提供 中缀表达式
 * @param dataProvider 及联数据提供对象, 传递的参数为当前已选中的 Item 级对象数据
 *//*
infix fun MultistageDataProvider.dataProvider(dataProvider: (selectedItem: ISelectAble) -> List<ISelectAble>): FinalSelected {
    selectDialog.setOnSelectListener { selected ->
        // Log.e("OnSelected", selected.toString())
        val preSendData = dataProvider(selected)
        selectDialog.dataReceiver?.send(if (preSendData.isEmpty()) null else preSendData)
    }
    return FinalSelected(selectDialog)
}


*/
/**
 * 及联选择数据 Dialog 的 最终选中操作 中缀表达式
 * @param finalOption 最终自己的操作, 传递的参数为当前的 selectDialog
 *//*
infix fun FinalSelected.finallySelected(finalOption: (dialog: DialogInterface, finalSelectedItem: ISelectAble) -> Unit): Reset {
    reSetOnclick { selectDialog }

    selectDialog.setOnFinalSelectListener { finalSelectedItem ->
        // Log.e("FinalSelectedListener", it.toString())

        finalOption(selectDialog, finalSelectedItem)
    }
    return Reset(selectDialog)
}

*/
/**  及联数据提供中间类  **//*
@Keep
data class MultistageDataProvider(
    */
/**  展示数据的及联 selectDialog 对象  **//*
    val selectDialog: BottomSelectDialog
)

*/
/**  及联数据最终选择中间类  **//*
@Keep
data class FinalSelected(
    */
/**  展示数据的及联 selectDialog 对象  **//*
    val selectDialog: BottomSelectDialog
)

@Keep
data class Reset(
    */
/**  展示数据的及联 selectDialog 对象  **//*
    val selectDialog: BottomSelectDialog
)*/

/*********************************************************************************/
/**                                                                             **/
/**                               Bitmap Extensions                             **/
/**                                                                             **/
/*********************************************************************************/

/**
 * 保存图片到指定路径并指定文件名
 * @param savePath 存储路径
 * @param fileName 文件名
 * @param format 存储格式, 默认为 jpg
 * @param quality 存储时压缩的大小, 默认不压缩
 */
fun Bitmap.saveTo(
    savePath: String,
    fileName: String,
    format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
    quality: Int = 100,
) {
    val file = File(savePath, fileName)
    if (file.exists()) file.delete()
    val fos = FileOutputStream(file)
    this.compress(format, quality, fos)
    fos.flush()
    fos.close()
}

/**
 * 保存图片到指定路径并指
 * @param savePath 存储路径
 * @param fileName 文件名
 * @param format 存储格式, 默认为 jpg
 * @param quality 存储时压缩的大小, 默认不压缩
 */
fun Bitmap.saveToGallery(
    context: Context, resource: Bitmap,
): Uri? {
    val resolver = context.contentResolver
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "自定义名称_${System.currentTimeMillis()}")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {//自定义路径
            val split = context.getPackageName().split(".")
            put(
                MediaStore.Images.Media.RELATIVE_PATH,
                Environment.DIRECTORY_PICTURES + "/" + split[split.size - 1] + "_pic") //不用默认路径 改为my_pic
        }


    }
    val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
    if (uri != null) {
        resolver.openOutputStream(uri).use {
            resource.compress(Bitmap.CompressFormat.JPEG, 100, it)
        }
    }

    return uri
}
@RequiresApi(Build.VERSION_CODES.Q)
 fun Uri.uriToFileQ(context: Context, uri: Uri): File? =
    if (uri.scheme == ContentResolver.SCHEME_FILE)
        File(requireNotNull(uri.path))
    else if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
        //把文件保存到沙盒
        val contentResolver = context.contentResolver
        val displayName = "${System.currentTimeMillis()}${Random().nextInt(9999)}.${
            MimeTypeMap.getSingleton()
                .getExtensionFromMimeType(contentResolver.getType(uri))}"
        val ios = contentResolver.openInputStream(uri)
        if (ios != null) {
            File("${context.cacheDir.absolutePath}/$displayName")
                .apply {
                    val fos = FileOutputStream(this)
                    FileUtils.copy(ios, fos)
                    fos.close()
                    ios.close()
                }
        } else null
    } else null


/**  给图片下放中央加水印文字  **/
fun Bitmap.addWatermarkToBottomCenter(
    text: String, @ColorInt textColor: Int = Color.WHITE,
    textSizePixels: Float = 34f,
): Bitmap {
    val canvas = Canvas(this)

    val textPaint = TextPaint()
    textPaint.isAntiAlias = true
    textPaint.color = textColor
    textPaint.textSize = textSizePixels

    @Suppress("DEPRECATION") val staticLayout = StaticLayout(
        text, textPaint, width - 8,
        Layout.Alignment.ALIGN_CENTER, 1.2f, 0f, false
    )

    // 设置水印位置
    canvas.translate(6f, height - 120f)
    staticLayout.draw(canvas)
    return this
}


/*********************************************************************************/
/**                                                                             **/
/**                               Network Extensions                            **/
/**                                                                             **/
/*********************************************************************************/

/**  将任意对象转换成 RequestBody 类型, 内容为 json  **/
fun Any.toJsonRequestBody() =
    this.toJson().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())

/**  将任意对象转换成 RequestBody 类型, 内容为 文本, 当对象为 String 时, 则直接传递, 当不为 String 时, 会先序列化成 json 然后再传递  **/
fun Any.toTextRequestBody() = when (this) {
    is String -> this.toRequestBody("text/plain".toMediaTypeOrNull())
    else -> this.toJson().toRequestBody("text/plain".toMediaTypeOrNull())
}

/**  将任意对象转换成 RequestBody 类型, 当前对象为 File 时, 内容为文件附件上传. 当前对象为 String 时, 直接上传文本, 当前对象为 Any 时, 先序列化成 json 然后再上传  **/
fun Any.toMultipartRequestBody() = when (this) {
    is File -> this.asRequestBody("multipart/form-data".toMediaTypeOrNull())
    is String -> this.toRequestBody("multipart/form-data".toMediaTypeOrNull())
    else -> this.toJson().toRequestBody("multipart/form-data".toMediaTypeOrNull())
}

/**  将任意对象转换成 json 串  **/
fun Any.toJson() = JsonTool.object2Json(this)


/*private val Fragment.bottomSelectDialog: BottomSelectDialog
    get() {
        val dialog = BottomSelectDialog(activity)
        return dialog
    }


*/
/**
 * 弹出底部 及联选择数据 Dialog
 * @param firstDataProvider Dialog 初次显示时的数据
 *//*
fun Fragment.multistageSelectDialog(
    firstDataProvider: () -> List<ISelectAble>
): MultistageDataProvider {
    val dialog = BottomSelectDialog(activity)
    dialog.init(activity, true)
    dialog.setOnRestClickListener { dialog.dismiss() }

    dialog.setDataProvider { position, receiver ->
        if (position == 0) {
            receiver?.send(firstDataProvider())
        }
    }
    dialog.show()
    return MultistageDataProvider(dialog)
}

infix fun FinalSelected.reSetOnclick(dialog: () -> BottomSelectDialog) {

}*/

/**
 * 创建指定数量的随机字符串
 *
 * @param numberFlag 是否是数字
 * @param length
 * @return String
 */
fun createRandom(numberFlag: Boolean, length: Int): String {
    var retStr = ""
    val strTable =
        if (numberFlag) "1234567890" else "1234567890abcdefghijkmnpqrstuvwxyz"
    val len = strTable.length
    var bDone = true
    do {
        retStr = ""
        var count = 0
        for (i in 0 until length) {
            val dblR = Math.random() * len
            val intR = Math.floor(dblR).toInt()
            val c = strTable[intR]
            if ('0' <= c && c <= '9') {
                count++
            }
            retStr += strTable[intR]
        }
        if (count >= 2) {
            bDone = false
        }
    } while (bDone)
    return retStr
}

/**
 * SHA加密
 *
 * @param strSrc 明文
 * @return 加密之后的密文
 */
fun SHA256(strSrc: String): String? {
    val digest = MessageDigest.getInstance("SHA-256")
    val result = digest.digest(strSrc.toByteArray())
    return toHex(result)
}

/**
 * byte数组转换为16进制字符串
 *
 * @param bts 数据源
 * @return 16进制字符串
 */
fun toHex(byteArray: ByteArray): String {
    val result = with(StringBuilder()) {
        byteArray.forEach {
            val hex = it.toInt() and (0xFF)
            val hexStr = Integer.toHexString(hex)
            if (hexStr.length == 1) {
                this.append("0").append(hexStr)
            } else {
                this.append(hexStr)
            }
        }
        this.toString()
    }
    //转成16进制后是32字节
    return result
}

//MIUI标识
private const val KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"
private const val KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name"
private const val KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage"

//EMUI标识
private const val KEY_EMUI_VERSION_CODE = "ro.build.version.emui"
private const val KEY_EMUI_API_LEVEL = "ro.build.hw_emui_api_level"
private const val KEY_EMUI_CONFIG_HW_SYS_VERSION = "ro.confg.hw_systemversion"

//Flyme标识
private const val KEY_FLYME_ID_FALG_KEY = "ro.build.display.id"
private const val KEY_FLYME_ID_FALG_VALUE_KEYWORD = "Flyme"
private const val KEY_FLYME_ICON_FALG = "persist.sys.use.flyme.icon"
private const val KEY_FLYME_SETUP_FALG = "ro.meizu.setupwizard.flyme"
private const val KEY_FLYME_PUBLISH_FALG = "ro.flyme.published"

/**
 * @param
 * @return ROM_TYPE ROM类型的枚举
 * @description获取ROM类型: MIUI_ROM, FLYME_ROM, EMUI_ROM, OTHER_ROM
 */
fun getRomType(): ROM_TYPE {
    val rom_type: ROM_TYPE = ROM_TYPE.OTHER
    try {
        val buildProperties: BuildProperties = BuildProperties.getInstance()
        if (buildProperties.containsKey(KEY_EMUI_VERSION_CODE) || buildProperties.containsKey(
                KEY_EMUI_API_LEVEL
            ) || buildProperties.containsKey(KEY_MIUI_INTERNAL_STORAGE)
        ) {
            return ROM_TYPE.EMUI
        }
        if (buildProperties.containsKey(KEY_MIUI_VERSION_CODE) || buildProperties.containsKey(
                KEY_MIUI_VERSION_NAME
            ) || buildProperties.containsKey(KEY_MIUI_VERSION_NAME)
        ) {
            return ROM_TYPE.MIUI
        }
        if (buildProperties.containsKey(KEY_FLYME_ICON_FALG) || buildProperties.containsKey(
                KEY_FLYME_SETUP_FALG
            ) || buildProperties.containsKey(KEY_FLYME_PUBLISH_FALG)
        ) {
            return ROM_TYPE.FLYME
        }
        if (buildProperties.containsKey(KEY_FLYME_ID_FALG_KEY)) {
            val romName: String = buildProperties.getProperty(KEY_FLYME_ID_FALG_KEY)
            if (!TextUtils.isEmpty(romName) && romName.contains(KEY_FLYME_ID_FALG_VALUE_KEYWORD)) {
                return ROM_TYPE.FLYME
            }
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
    return rom_type
}

enum class ROM_TYPE {
    MIUI, FLYME, EMUI, OTHER
}

fun getRequestBody(hashMap: HashMap<String, String>): RequestBody {
    val data = StringBuffer()
    if (hashMap != null && hashMap.size > 0) {

        hashMap.forEach {

            data.append(it.key).append("=").append(it.value).append("&")


        }
    }
    val jso = data.substring(0, data.length - 1)
    return RequestBody.create(
        "application/json; charset=utf-8".toMediaTypeOrNull(),
        jso
    )
}

/***
 * 等待ms毫秒，期间可以使用countDownLatch主动提前唤醒，结束等待。
 *
 *
 * @param ms 延迟的毫秒数
 * @return 如果最终是被主动唤醒的，则返回true，否则返回false
 */
suspend fun delayInAdvance(ms: Long, countDownLatch: CountDownLatch, handlerRunnable: Handler?) {
    GlobalScope.async {
        var what = 1

/* var what = Random.nextInt(1..1000000)
 whil(handlerRunnable.hasMessages(what)) {
     what = Random.nextInt(1..1000000)
 }*/
        handlerRunnable?.sendMessageDelayed(handlerRunnable.obtainMessage(what).apply {
            obj = Runnable {
                LogUtils.d("扫描到的卡号是:11")

                countDownLatch.countDown()
            }
        }, ms)
        countDownLatch.await()
        handlerRunnable?.hasMessages(what)
    }.await()
}


private val platforms = arrayOf(
    "http://pv.sohu.com/cityjson",
    "http://pv.sohu.com/cityjson?ie=utf-8",
    "http://ip.chinaz.com/getip.aspx"
)

fun getOutNetIP(context: Context?, index: Int): String? {
    var index = index
    if (index < platforms.size) {
        var buff: BufferedReader? = null
        var urlConnection: HttpURLConnection? = null
        try {
            val url = URL(platforms[index])
            urlConnection = url.openConnection() as HttpURLConnection
            urlConnection.requestMethod = "GET"
            urlConnection!!.readTimeout = 5000 //读取超时
            urlConnection.connectTimeout = 5000 //连接超时
            urlConnection.doInput = true
            urlConnection.useCaches = false
            val responseCode = urlConnection.responseCode
            if (responseCode == HttpURLConnection.HTTP_OK) { //找到服务器的情况下,可能还会找到别的网站返回html格式的数据
                val `is` = urlConnection.inputStream
                buff = BufferedReader(InputStreamReader(`is`, "UTF-8")) //注意编码，会出现乱码
                val builder = java.lang.StringBuilder()
                var line: String? = null
                while (buff.readLine().also { line = it } != null) {
                    builder.append(line)
                }
                buff.close() //内部会关闭 InputStream
                urlConnection.disconnect()
                Log.e("xiaoman", builder.toString())
                if (index == 0 || index == 1) {
                    //截取字符串
                    val satrtIndex = builder.indexOf("{") //包含[
                    val endIndex = builder.indexOf("}") //包含]
                    val json =
                        builder.substring(satrtIndex, endIndex + 1) //包含[satrtIndex,endIndex)
                    val jo = JSONObject(json)
                    return jo.getString("cip")
                } else if (index == 2) {
                    val jo = JSONObject(builder.toString())
                    return jo.getString("ip")
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    } else {
        return "0.0.0.0"
    }
    return getOutNetIP(context, ++index)


}


/**
 * 获取剪切板内容
 */
fun Any.getClipboardContent(): String? {
    // 获取系统剪贴板
    val clipboard: ClipboardManager =
        BaseApp.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    // 返回数据
    val clipData: ClipData? = clipboard.primaryClip
    if (clipData == null || clipData.itemCount <= 0) {
        return ""
    }
    val item = clipData.getItemAt(0)
    /**
     *清空剪切板
     */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        //要api28以上
        clipboard.clearPrimaryClip()
    } else {
        clipboard.setPrimaryClip(ClipData(null))
    }
    return if (item == null || item.text == null) {
        ""
    } else item.text.toString()
}

/**
 * 设置剪切板内容
 */
fun Any.setClipboardContent(content: String) {
    //获取ClipboardManager对象
    val clipboard = BaseApp.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
//把文本封装到ClipData中
    val clip = ClipData.newPlainText(null, content) //这里的第一个参数label通常设置为null

// Set the clipboard's primary clip.
    clipboard!!.setPrimaryClip(clip)

}

/**
 *跳转设置页面
 */

fun Any.jumpToAccessibilitySetting(context: Context) {
    val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
    context.startActivity(intent)
}


fun Disposable.bindLifecycle(lifecycle: Lifecycle?) {
    if (lifecycle == null) return
    lifecycle.addObserver(LifecycleDisposable(lifecycle, this))
}

val View.activity: Activity?
    get() = getActivity(context)

// https://stackoverflow.com/questions/9273218/is-it-always-safe-to-cast-context-to-activity-within-view/45364110
private fun getActivity(context: Context?): Activity? {
    if (context == null) return null
    if (context is Activity) return context
    if (context is ContextWrapper) return getActivity(context.baseContext)
    return null
}

val View.lifecycleOwner: LifecycleOwner?
    get() {
        val activity = activity as? FragmentActivity? ?: return null
        val fragment = findSupportFragment(this, activity)
        return fragment?.viewLifecycleOwner ?: activity
    }
private val tempViewToSupportFragment = ArrayMap<View, Fragment>()
private fun findSupportFragment(target: View, activity: FragmentActivity): Fragment? {
    tempViewToSupportFragment.clear()
    findAllSupportFragmentsWithViews(
        activity.supportFragmentManager.fragments, tempViewToSupportFragment
    )
    var result: Fragment? = null
    val activityRoot = activity.findViewById<View>(android.R.id.content)
    var current = target
    while (current != activityRoot) {
        result = tempViewToSupportFragment[current]
        if (result != null) {
            break
        }
        current = if (current.parent is View) {
            current.parent as View
        } else {
            break
        }
    }
    tempViewToSupportFragment.clear()
    return result
}

private fun findAllSupportFragmentsWithViews(
    topLevelFragments: Collection<Fragment?>?, result: MutableMap<View?, Fragment>,
) {
    if (topLevelFragments == null) {
        return
    }
    for (fragment in topLevelFragments) {
        // getFragment()s in the support FragmentManager may contain null values, see #1991.
        if (fragment?.view == null) {
            continue
        }
        result[fragment.view] = fragment
        findAllSupportFragmentsWithViews(fragment.childFragmentManager.fragments, result)
    }
}

fun ViewGroup.inflate(resId: Int): View {
    return LayoutInflater.from(context).inflate(resId, this, false)
}

val View.isInScreen: Boolean
    get() = ViewCompat.isAttachedToWindow(this) && visibility == View.VISIBLE && getLocalVisibleRect(
        Rect())

//RecyclerView 页可见性检测
fun RecyclerView.onItemVisibilityChange(
    percent: Float = 0.5f,
    viewGroups: List<ViewGroup>? = null,
    block: (itemView: View, adapterIndex: Int, isVisible: Boolean) -> Unit,
) {
    val childVisibleRect = Rect()
    val visibleAdapterIndexs = mutableSetOf<Int>()
    val checkVisibility = {
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val adapterIndex = getChildAdapterPosition(child)
            if (adapterIndex == NO_POSITION) continue
            val isChildVisible = child.getLocalVisibleRect(childVisibleRect)
            val visibleArea = childVisibleRect.let { it.height() * it.width() }
            val realArea = child.width * child.height
            if (this.isInScreen && isChildVisible && visibleArea >= realArea * percent) {
                if (visibleAdapterIndexs.add(adapterIndex)) {
                    block(child, adapterIndex, true)
                }
            } else {
                if (adapterIndex in visibleAdapterIndexs) {
                    block(child, adapterIndex, false)
                    visibleAdapterIndexs.remove(adapterIndex)
                }
            }
        }
    }
    val scrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            checkVisibility()
        }
    }
    addOnScrollListener(scrollListener)

    fun View.onVisibilityChange(
        viewGroups: List<ViewGroup> = emptyList(), // 会被插入 Fragment 的容器集合
        needScrollListener: Boolean = true,
        block: (view: View, isVisible: Boolean) -> Unit,
    ) {
        val KEY_VISIBILITY = "KEY_VISIBILITY".hashCode()
        val KEY_HAS_LISTENER = "KEY_HAS_LISTENER".hashCode()
        // 若当前控件已监听可见性，则返回
        if (getTag(KEY_HAS_LISTENER) == true) return

        // 检测可见性
        val checkVisibility = {
            // 获取上一次可见性
            val lastVisibility = getTag(KEY_VISIBILITY) as? Boolean
            // 判断控件是否出现在屏幕中
            val isInScreen = this.isInScreen
            // 首次可见性变更
            if (lastVisibility == null) {
                if (isInScreen) {
                    block(this, true)
                    setTag(KEY_VISIBILITY, true)
                }
            }
            // 非首次可见性变更
            else if (lastVisibility != isInScreen) {
                block(this, isInScreen)
                setTag(KEY_VISIBILITY, isInScreen)
            }
        }

        // 全局重绘监听器
        class LayoutListener : ViewTreeObserver.OnGlobalLayoutListener {
            // 标记位用于区别是否是遮挡case
            var addedView: View? = null
            override fun onGlobalLayout() {
                // 遮挡 case
                if (addedView != null) {
                    // 插入视图矩形区域
                    val addedRect = Rect().also { addedView?.getGlobalVisibleRect(it) }
                    // 当前视图矩形区域
                    val rect = Rect().also { this@onVisibilityChange.getGlobalVisibleRect(it) }
                    // 如果插入视图矩形区域包含当前视图矩形区域，则视为当前控件不可见
                    if (addedRect.contains(rect)) {
                        block(this@onVisibilityChange, false)
                        setTag(KEY_VISIBILITY, false)
                    } else {
                        block(this@onVisibilityChange, true)
                        setTag(KEY_VISIBILITY, true)
                    }
                }
                // 非遮挡 case
                else {
                    checkVisibility()
                }
            }
        }

        val layoutListener = LayoutListener()
        // 编辑容器监听其插入视图时机
        viewGroups.forEachIndexed { index, viewGroup ->
            viewGroup.setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
                override fun onChildViewAdded(parent: View?, child: View?) {
                    // 当控件插入，则置标记位
                    layoutListener.addedView = child
                }

                override fun onChildViewRemoved(parent: View?, child: View?) {
                    // 当控件移除，则置标记位
                    layoutListener.addedView = null
                }
            })
        }
        viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
        // 全局滚动监听器
        var scrollListener: ViewTreeObserver.OnScrollChangedListener? = null
        if (needScrollListener) {
            scrollListener = ViewTreeObserver.OnScrollChangedListener { checkVisibility() }
            viewTreeObserver.addOnScrollChangedListener(scrollListener)
        }
        // 全局焦点变化监听器
        val focusChangeListener = ViewTreeObserver.OnWindowFocusChangeListener { hasFocus ->
            val lastVisibility = getTag(KEY_VISIBILITY) as? Boolean
            val isInScreen = this.isInScreen
            if (hasFocus) {
                if (lastVisibility != isInScreen) {
                    block(this, isInScreen)
                    setTag(KEY_VISIBILITY, isInScreen)
                }
            } else {
                if (lastVisibility == true) {
                    block(this, false)
                    setTag(KEY_VISIBILITY, false)
                }
            }
        }
        viewTreeObserver.addOnWindowFocusChangeListener(focusChangeListener)
        // 为避免内存泄漏，当视图被移出的同时反注册监听器
        addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View?) {
            }

            override fun onViewDetachedFromWindow(v: View?) {
                v ?: return
                // 有时候 View detach 后，还会执行全局重绘，为此退后反注册
                post {
                    try {
                        v.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
                    } catch (_: java.lang.Exception) {
                        v.viewTreeObserver.removeGlobalOnLayoutListener(layoutListener)
                    }
                    v.viewTreeObserver.removeOnWindowFocusChangeListener(focusChangeListener)
                    if (scrollListener != null) v.viewTreeObserver.removeOnScrollChangedListener(
                        scrollListener)
                    viewGroups.forEach { it.setOnHierarchyChangeListener(null) }
                }
                removeOnAttachStateChangeListener(this)
            }
        })
        // 标记已设置监听器
        setTag(KEY_HAS_LISTENER, true)
    }
    // 为列表添加全局可见性检测
    if (viewGroups != null) {
        onVisibilityChange(viewGroups, false) { view, isVisible ->
            // 当列表可见时，检测其表项的可见性
            if (isVisible) {
                checkVisibility()
            } else {
                // 当列表不可见时，回调所有可见表项为不可见
                for (i in 0 until childCount) {
                    val child = getChildAt(i)
                    val adapterIndex = getChildAdapterPosition(child)
                    if (adapterIndex in visibleAdapterIndexs) {
                        block(child, adapterIndex, false)
                        visibleAdapterIndexs.remove(adapterIndex)
                    }
                }
            }
        }
    }
    addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View?) {
        }

        override fun onViewDetachedFromWindow(v: View?) {
            if (v == null || v !is RecyclerView) return
            v.removeOnScrollListener(scrollListener)
            removeOnAttachStateChangeListener(this)
        }
    })
}

fun ViewPager2.addOnPageVisibilityChangeListener(block: (index: Int, isVisible: Boolean) -> Unit) {
    // 当前页
    var lastPage: Int = currentItem
    // 注册页滚动监听器
    val listener = object : ViewPager2.OnPageChangeCallback() {
        override fun onPageSelected(position: Int) {
            // 回调上一页不可见
            if (lastPage != position) {
                block(lastPage, false)
            }
            // 回调当前页可见
            block(position, true)
            lastPage = position
        }
    }
    registerOnPageChangeCallback(listener)
    // 避免内存泄漏
    addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View?) {
        }

        override fun onViewDetachedFromWindow(v: View?) {
            if (v == null || v !is ViewPager2) return
            if (ViewCompat.isAttachedToWindow(v)) {
                v.unregisterOnPageChangeCallback(listener)
            }
            removeOnAttachStateChangeListener(this)
        }

    })


}

fun TextView.setTextColorGradient(@ColorRes startColor: Int, @ColorRes endColor: Int) {
    val context: Context = getContext()
    @ColorInt val sc = context.resources.getColor(startColor)
    @ColorInt val ec = context.resources.getColor(endColor)
    val x: Float = paint.textSize * text.length
    val gradient = LinearGradient(0f, 0f, x, 0f, sc, ec, Shader.TileMode.CLAMP)
    paint.shader = gradient
    invalidate()
}


fun TextView.setTextColorGradient(colors: IntArray, positions: FloatArray) {
    val text: String = getText().toString()
    // 方法1：getTextBounds
    val rect = Rect()
    paint.getTextBounds(text, 0, text.length, rect)
    // 方法2：measureText
//        float measuredWidth = textView.getPaint().measureText(text);
    // 方法2：measureText
//        float measuredWidth = textView.getPaint().measureText(text);
    val textWidth = rect.width().toFloat()
    val linearGradient = LinearGradient(0f, 0f, textWidth, 0f,
        colors,
        positions,
        Shader.TileMode.REPEAT)
    paint.shader = linearGradient
    invalidate()
}

fun TextView.clearTextColorGradient() {
    paint.shader = null;
    invalidate();
}
